﻿namespace Pps.Gui.Lcd
{
    using System;
    using GHI.Premium.Hardware;
    using Microsoft.SPOT.Hardware;
    using Pps.Core;

    public delegate void HomeButtonEventHandler(TouchId finger);
    public delegate void MenuButtonEventHandler(TouchId finger);
    public delegate void BackButtonEventHandler(TouchId finger);

    public sealed class CP7 : Display
    {
        private const int WIDTH = 800;
        private const int HEIGHT = 480;

        private const int MAX_FINGER = 1;

        private const byte DEVICE_MODE = 0x00;
        private const byte GESTURE_ID = 0x01;
        private const byte TD_STATUS = 0x02;

        // TOUCH_X and TOUCH_Y are alternating, ordering is as follows:
        // TOUCH1_XH - 0x03
        // TOUCH1_XL - 0x04
        // TOUCH1_YH - 0x05
        // TOUCH1_YL - 0x06
        //           - 0x07
        //           - 0x08
        private const byte TOUCH_X = 0x03; // start 0x03 to 0x1C
        private const byte TOUCH_Y = 0x05; // start 0x05 to 0x1E

        private const byte ID_G_TIME_ENTER_MONITOR = 0x87;
        private const byte ID_G_PERIODACTIVE = 0x88;
        private const byte ID_G_PERIODMONITOR = 0x89;
        private const byte ID_G_MODE = 0xA4;

        public event HomeButtonEventHandler HomePressed;
        public event MenuButtonEventHandler MenuPressed;
        public event BackButtonEventHandler BackPressed;

        private OutputPort backlight;
        private InterruptPort touchInterrupt;
        private Cpu.Pin touchPin;

        public CP7(Cpu.Pin touchPin, Cpu.Pin backlightPin)
            : base()
        {
            this.touchPin = touchPin;

            this.backlight = new OutputPort(backlightPin, true);

            this.Initialize();
        }

        protected override void ConfigureTouch()
        {
            if (SystemInfo.SystemID.SKU != 3)
            {
                //WriteRegister(ID_G_TIME_ENTER_MONITOR, 2);
                //WriteRegister(ID_G_PERIODACTIVE, 12);

                /* Setting the interrupt mode
                 * 0 = query mode -> only a edge flip on touchdown and touch up, low at touchmove
                 * 1 (default) = trigger mode -> after every data packet a edge flip, on G120 this mode is a major impact in performance
                 * and it can happen that it gets stuck
                 */
                WriteRegister(ID_G_MODE, 0);

                this.touchInterrupt = new InterruptPort(touchPin, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);
                this.touchInterrupt.OnInterrupt += this.TouchInterrupt;
            }
        }

        protected override void ConfigureDisplay()
        {
            if (SystemInfo.SystemID.SKU != 3)
            {
                Configuration.LCD.Configurations lcdConfig = new Configuration.LCD.Configurations();

                lcdConfig.Width = 800;
                lcdConfig.Height = 480;

                lcdConfig.OutputEnableIsFixed = true;
                lcdConfig.OutputEnablePolarity = true;

                lcdConfig.HorizontalSyncPolarity = true;
                lcdConfig.VerticalSyncPolarity = true;
                lcdConfig.PixelPolarity = false;

                lcdConfig.HorizontalSyncPulseWidth = 1;
                lcdConfig.HorizontalBackPorch = 46;
                lcdConfig.HorizontalFrontPorch = 16;
                lcdConfig.VerticalSyncPulseWidth = 1;
                lcdConfig.VerticalBackPorch = 23;
                lcdConfig.VerticalFrontPorch = 7;

                lcdConfig.PixelClockRateKHz = 24000;

                // Set config
                if (Configuration.LCD.Set(lcdConfig))
                {
                    PowerState.RebootDevice(false);
                }
            }
        }

        private int GetNumberOfTouches()
        {
            return ReadRegister(TD_STATUS) & 0xF;
        }

        private Touch GetTouch(int id)
        {
            if (id < MAX_FINGER)
            {
                int x_data = (ReadRegister(TOUCH_X + id * 6) << 8) + ReadRegister((TOUCH_X + 1) + id * 6);
                int y_data = (ReadRegister(TOUCH_Y + id * 6) << 8) + ReadRegister((TOUCH_Y + 1) + id * 6);

                return new Touch((TouchId)(y_data >> 12), (x_data & 0xFFF), (y_data & 0xFFF), (TouchFlag)(x_data >> 14));
            }

            return null;
        }

        private void TouchInterrupt(uint port, uint state, DateTime time)
        {
            if (this.backlight.Read() == true)
            {
                this.ProcessInterrupt();
            }

            this.ResetBacklightTimer();
        }

        private void ProcessInterrupt()
        {
            for (int i = 0; i < MAX_FINGER; i++)
            {
                // Update touchinformation
                Touch data = this.GetTouch(i);

                if (data == null || data.Id == TouchId.Unknown || data.Flag == TouchFlag.Unknown)
                {
                    break;
                }

                if (data.Flag == TouchFlag.PutDown)
                {
                    if (data.X > 800)
                    {
                        if (data.Y >= 0 && data.Y <= 50)
                        {
                            // Home
                            this.OnHomePressed(data);
                        }
                        if (data.Y >= 100 && data.Y <= 150)
                        {
                            // Menu
                            this.OnMenuPressed(data);
                        }
                        else if (data.Y >= 200 && data.Y <= 250)
                        {
                            // Back
                            this.OnBackPressed(data);
                        }
                    }
                    else
                    {
                        this.OnTouchDown(data);
                    }
                }

                if (data.Flag == TouchFlag.PutUp)
                {
                    this.OnTouchUp(data);
                }

                if (data.Flag == TouchFlag.Contact)
                {
                    this.OnTouchMove(data);
                }
            }
        }

        private void OnMenuPressed(Touch data)
        {
            if (this.MenuPressed != null)
            {
                this.MenuPressed(data.Id);
            }
        }

        private void OnHomePressed(Touch data)
        {
            if (this.HomePressed != null)
            {
                this.HomePressed(data.Id);
            }
        }

        private void OnBackPressed(Touch data)
        {
            if (this.BackPressed != null)
            {
                this.BackPressed(data.Id);
            }
        }

        public override void BacklightTurnOn()
        {
            if (this.backlight.Read() == false)
            {
                this.OnBacklightOn();
                this.backlight.Write(true);
            }
        }

        public override void BacklightTurnOff()
        {
            if (this.backlight.Read() == true)
            {
                this.backlight.Write(false);
                this.OnBacklightOff();
            }
        }

        private static int ReadRegister(int Address)
        {
            return (int)ReadRegister((byte)Address);
        }

        private static byte ReadRegister(byte Address)
        {
            byte[] i2cData = new byte[1] { 0 };

            using (I2CDevice i2c = new I2CDevice(new I2CDevice.Configuration(0x38, 400)))
            {
                I2CDevice.I2CTransaction[] i2cTransactions = new I2CDevice.I2CTransaction[2];

                try
                {
                    i2cTransactions[0] = (I2CDevice.I2CTransaction)I2CDevice.CreateWriteTransaction(new byte[1] { Address });
                    i2cTransactions[1] = (I2CDevice.I2CTransaction)I2CDevice.CreateReadTransaction(i2cData);

                    if (i2c.Execute(i2cTransactions, 200) == 0)
                    {
                        Debug.Print("Failed to perform I2C transaction");
                    }

                }
                catch (Exception ex)
                {
                    Debug.Print(ex.ToString());
                }
            }

            return i2cData[0];
        }

        private static void WriteRegister(byte Address, byte value)
        {
            using (I2CDevice i2c = new I2CDevice(new I2CDevice.Configuration(0x38, 400)))
            {
                I2CDevice.I2CTransaction[] i2cTran = new I2CDevice.I2CTransaction[1];
                i2cTran[0] = (I2CDevice.I2CTransaction)I2CDevice.CreateWriteTransaction(new byte[2] { Address, value });

                if (i2c.Execute(i2cTran, 500) == 0)
                {
                    Debug.Print("Failed to perform I2C transaction");
                }
            }
        }
    }
}